﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading.Tasks;
using Red.Controls.TimeLine.Commands;
using Red.Controls.TimeLine.Core;
using Red.Controls.ViewModels;

namespace Red.Controls.TimeLine.ViewModels
{
    #region Track Base

    public abstract class TrackBase : TimeLineItemBase
    {
        #region Data

        private readonly TracksRoot _root = null;

        private double _trackDisplayHeight = 90;
        private bool _isSelected = false;
        private bool _isExpanded = false;

        #endregion

        #region C-tor

        public TrackBase( TracksRoot root, TimeLineId id, TimeLineControler controler )
            : base( id, controler )
        {
            _root = root;

        }

        #endregion

        #region Properties

        public TracksRoot Root
        {
            get
            {
                if ( _root != null )
                    return _root;

                if ( this is TracksRoot )
                    return this as TracksRoot;

                return null;
            }
        }

        public double TrackDisplayHeight
        {
            get { return _trackDisplayHeight; }
            set
            {
                if ( _trackDisplayHeight != value )
                {
                    _trackDisplayHeight = value;
                    OnPropertyChanged();
                }
            }
        }

        public bool IsSelected
        {
            get { return _isSelected; }
            set
            {
                if ( _isSelected != value )
                {
                    _isSelected = value;
                    OnPropertyChanged();
                }
            }
        }

        public bool IsExpanded
        {
            get { return _isExpanded; }
            set
            {
                if ( _isExpanded != value )
                {
                    _isExpanded = value;
                    OnPropertyChanged();
                }
            }
        }

        public virtual IEnumerable<RedMenuDataItem> TracksHeaderContextMenuItems
        {
            get { return new List<RedMenuDataItem>(); }
        }

        public virtual IEnumerable<RedMenuDataItem> TracksTreeContextMenuItems
        {
            get { return new List<RedMenuDataItem>(); }            
        }

        #endregion

        #region Internal methods

        /// <summary>
        /// Removes all children and recreated them
        /// </summary>
        internal abstract void Rebuild();

        #endregion
    }

    #endregion

    #region Track

    /// <summary>
    /// A simple track that is TrackItemBase (events) container. Cannot have subtrack.
    /// </summary>
    public class Track : TrackBase
    {
        #region Data

        private readonly ObservableCollection< TrackItemBase > _items = new ObservableCollection< TrackItemBase >();

        #endregion

        #region C-tor

        public Track( TracksRoot root, TimeLineId id, TimeLineControler controler )
            : base( root, id, controler )
        {
            TrackDisplayHeight = 35;
        }

        #endregion

        #region Properites

        public void AttachTrackObject( TrackItemBase item )
        {
            _items.Add( item );
        }

        public void DettachTrackObject( TrackItemBase item )
        {
            _items.Remove( item );
        }

        public IEnumerable<TrackItemBase> Children
        {
            get { return _items; }
        }

        public bool HasTackItems
        {
            get { return _items.Count != 0; }
        }

        public override IEnumerable<RedMenuDataItem> TracksHeaderContextMenuItems
        {
            get
            {
                List<RedMenuDataItem> menu = new List<RedMenuDataItem>();
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.RenameTrack, this ) ) );
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.DeleteTrack, this ) ) );

                return menu;
            }
        }

        public override IEnumerable<RedMenuDataItem> TracksTreeContextMenuItems
        {
            get
            {
                List<RedMenuDataItem> menu = new List<RedMenuDataItem>();
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.AddPointEvent, this ) ) );
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.AddDurationEvent, this ) ) );

                return menu;
            }
        }

        #endregion

        #region Public methods

        public TrackItemBase GetItemById( TimeLineId id )
        {
            foreach ( TrackItemBase trackObject in _items )
            {
                if ( trackObject.Id == id )
                    return trackObject;
            }
            return null;
        }

        public override void RecalculateVisualsProperties()
        {
            foreach ( TimeLineItemBase trackObject in _items )
            {
                trackObject.RecalculateVisualsProperties();
            }
        }

        #endregion

        #region Internal methods

        internal void RemovedTrackItem( TimeLineId id )
        {
            TrackItemBase item = GetItemById( id );
            if ( item != null )
            {
                _items.Remove( item );
            }
        }

        internal async Task CreatedTrackItem( TimeLineId id )
        {
            _items.Add( await CreateTrackItemInternalAsync( id ) );
        }

        internal override async void Rebuild()
        {
            // Creates events or other objects here!

            _items.Clear();

            TimeLineBackendProxy backend = Root.Backend;

            List<TimeLineId> items = new List<TimeLineId>();
            await backend.GetTrackEventsAsync( Id, items );

            if ( items.Count != 0 )
            {
                List<Task> tasks = new List<Task>();

                foreach ( TimeLineId id in items )
                {
                    Task<TrackItemBase> task = CreateTrackItemInternalAsync( id );
                    tasks.Add( task );

                    _items.Add( await task );
                }

                // wait for all children before continue
                await Task.WhenAll( tasks.ToArray() );               
            }
        }

        private async Task<TrackItemBase> CreateTrackItemInternalAsync( TimeLineId id )
        {
            TimeLineBackendProxy backend = Root.Backend;
            string type = ( await backend.GetEventTypeAsync( id ) ).Value;

            TrackItemBase item = null;
            if ( type == "PointEvent" )
            {
                item = new PointTrackEvent( id, TimeLineControler );
            }
            else if ( type == "DurationEvent" )
            {
                item = new DurationTrackEvent( id, TimeLineControler );
            }

            if ( item == null )
            {
                throw new NullReferenceException();
            }

            item.DataModel = await backend.GetEventRawDataAsync( id );
            return item;
        }

        #endregion
    }

    #endregion

    #region Tracks Group

    public class TracksGroup : TrackBase
    {
        #region Data

        private readonly ObservableCollection< TrackBase > _tracks = new ObservableCollection< TrackBase >();

        #endregion

        #region C-tor

        public TracksGroup( TracksRoot root, TimeLineId id, TimeLineControler controler )
            : base( root, id, controler )
        {
            TrackDisplayHeight = 20;
        }

        #endregion

        #region Properites

        public IEnumerable< TrackBase > Children
        {
            get { return _tracks; }
        }

        public bool HasChildren
        {
            get { return _tracks.Count != 0; }
        }

        public override IEnumerable<RedMenuDataItem> TracksHeaderContextMenuItems
        {
            get
            {
                List<RedMenuDataItem> menu = new List<RedMenuDataItem>();
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.AddTrack, this ) ) );
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.RenameTrack, this ) ) );
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.DeleteTrack, this ) ) );

                return menu;
            }
        }

        #endregion

        public override void RecalculateVisualsProperties()
        {
            foreach ( TrackBase track in _tracks )
            {
                track.RecalculateVisualsProperties();
            }
        }

        #region Internal Methods

        internal override async void Rebuild()
        {
            ClearTracks();

            TimeLineBackendProxy backend = Root.Backend;

            List<TimeLineId> children = new List<TimeLineId>();
            await backend.GetTrackSubtracksAsync( Id, children );

            if ( children.Count != 0 )
            {
                List<Task> tasks = new List<Task>();

                foreach ( TimeLineId id in children )
                {
                    Task<TrackBase> task = CreateTrackInternalAsync( id );
                    tasks.Add( task );

                    _tracks.Add( await task );
                }

                // wait for all children before continue
                await Task.WhenAll( tasks.ToArray() );

                foreach ( TrackBase track in _tracks )
                {
                    track.Rebuild();
                }
            }
        }

        internal void ClearTracks()
        {
            foreach ( TrackBase item in _tracks )
            {
                Root.UnregisterTrack( item );
            }
            _tracks.Clear();
        }
      
        internal void RemovedTrack( TimeLineId id )
        {
            TrackBase track = GetTrackById( id );
            if ( track != null )
            {
                Root.UnregisterTrack( track );
                _tracks.Remove( track );
            }
        }

        internal async Task CreatedTrack( TimeLineId id )
        {
            _tracks.Add( await CreateTrackInternalAsync( id ) );
            IsExpanded = true;
        }

        internal TrackBase GetTrackById( TimeLineId id )
        {
            foreach ( TrackBase track in _tracks )
            {
                if ( track.Id == id )
                {
                    return track;
                }
            }
            return null;
        }

        #endregion

        #region Private methods

        private async Task<TrackBase> CreateTrackInternalAsync( TimeLineId id )
        {
            TimeLineBackendProxy backend = Root.Backend;
            string type = ( await backend.GetTrackTypeAsync( id ) ).Value;

            TrackBase track = Root.CreateTrack( id, type );
            track.DataModel = await backend.GetTrackRawDataAsync( id );

            return track;
        }

        #endregion
    }

    #endregion

    #region Tracks Root

    public class TracksRoot : TracksGroup
    {
        #region Data

        private readonly TimeLineBackendProxy _backend = null;

        /// <summary>
        /// Global map of all Tracks created by this tree
        /// </summary>
        private readonly Dictionary<TimeLineId, TrackBase> _tree = new Dictionary<TimeLineId, TrackBase>();

        #endregion

        #region C-tor

        public TracksRoot( TimeLineBackendProxy backend, TimeLineControler controler ) 
            : base( null, backend.GetTracksRootId(), controler )
        {
            _backend = backend;
            RegisterTrack( this );
        }

        #endregion
      
        #region Properties

        public TimeLineBackendProxy Backend
        {
            get { return _backend; }
        }

        #endregion

        #region Public Methods

        public override IEnumerable<RedMenuDataItem> TracksTreeContextMenuItems
        {
            get
            {
                List<RedMenuDataItem> menu = new List<RedMenuDataItem>();
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.AddPointEvent, this ) ) );
                menu.Add( new RedMenuDataItem( new CommandWrapper( TimeLineCommands.AddDurationEvent, this ) ) );

                return menu;
            }
        }
     
        /// <summary>
        /// TimeLine backend created a track
        /// </summary>
        public Task OnTrackCreatedAsync( TimeLineId parentId, TimeLineId trackId )
        {
            TrackBase parentBaseTrack = FindTrackInTree( parentId );
            if ( ( parentBaseTrack != null ) && ( parentBaseTrack is TracksGroup ) )
            {
                TracksGroup parentTrack = parentBaseTrack as TracksGroup;
                return parentTrack.CreatedTrack( trackId );
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// TimeLine backend created a track
        /// </summary>
        public Task OnTrackRemovedAsync( TimeLineId parentId, TimeLineId trackId )
        {
            TrackBase parentBaseTrack = FindTrackInTree( parentId );
            if ( ( parentBaseTrack != null ) && ( parentBaseTrack is TracksGroup ) )
            {
                TracksGroup parentTrack = parentBaseTrack as TracksGroup;
                parentTrack.RemovedTrack( trackId );
            }

            return Task.CompletedTask;
        }

        public Task OnEventCreatedAsync( TimeLineId parentId, TimeLineId eventId )
        {
            TrackBase parentBaseTrack = FindTrackInTree( parentId );
            if ( ( parentBaseTrack != null ) && ( parentBaseTrack is Track ) )
            {
                Track parentTrack = parentBaseTrack as Track;
                return parentTrack.CreatedTrackItem( eventId );
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// TimeLine backend created a track
        /// </summary>
        public Task OnEventRemovedAsync( TimeLineId parentId, TimeLineId eventId )
        {
            TrackBase parentBaseTrack = FindTrackInTree( parentId );
            if ( ( parentBaseTrack != null ) && ( parentBaseTrack is Track ) )
            {
                Track parentTrack = parentBaseTrack as Track;
                parentTrack.RemovedTrackItem( eventId );
            }

            return Task.CompletedTask;
        }

        #endregion

        #region Internal methods

        internal override void Rebuild()
        {
            _tree.Clear();
            RegisterTrack( this );

            base.Rebuild();
        }

        /// <summary>
        /// A factory method. Called by Tracks object internally!
        /// </summary>
        internal TrackBase CreateTrack( TimeLineId id, string type )
        {
            TrackBase track = FindTrackInTree( id );
            if ( track == null )
            {
                if ( type == "Track" )
                {
                    track = new Track( this, id, TimeLineControler );
                }
                else if ( type == "TracksGroup" )
                {
                    track = new TracksGroup( this, id, TimeLineControler );
                }

                if ( track == null )
                {
                    throw new NullReferenceException();
                }                
                RegisterTrack( track );
            }
            return track;
        }
        
        /// <summary>
        /// Called by Tracks object internally!
        /// </summary>
        internal void UnregisterTrack( TrackBase track )
        {
            if ( track != null )
            {
                _tree.Remove( track.Id );
            }
        }

        #endregion

        #region Private Methods

        private TrackBase FindTrackInTree( TimeLineId id )
        {
            TrackBase result = null;
            if ( _tree.TryGetValue( id, out result ) )
            {
                return result;
            }
            return null;
        }

        /// <summary>
        /// Called by Tracks object internally
        /// </summary>
        private void RegisterTrack( TrackBase track )
        {
            if ( track != null )
            {
                TrackBase result = null;
                if ( _tree.TryGetValue( track.Id, out result ) )
                {
                    Debug.Assert( false, "Track already registred" );
                    return;
                }
                _tree.Add( track.Id, track );
            }
        }

        #endregion
    }

    #endregion
}